package sh.grapendaal.tsuushin.module.uno.engine;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Set;

import sh.grapendaal.tsuushin.module.uno.engine.Card.Color;
import sh.grapendaal.tsuushin.module.uno.engine.Card.Rank;

/**
 * This class will completely handle a single game of Uno. It keeps track of all
 * the participating players, the cards and game rules.
 */
public class UnoEngine {
	private static final int MINIMAL_PLAYERS_NEEDED = 2;
	private static final int NUM_WILDCARDS = 4;
	private static final int NUM_CARDS_PER_PLAYER = 10;

	/**
	 * 
	 */
	private CommunicationPipeline communication;

	/**
	 * The players currently in the game. A LinkedHashSet was chosen because
	 * player have to be unique and the order of the players should be
	 * consistent. The players will be shuffled when the game starts.
	 */
	private Set<Player> players = new LinkedHashSet<Player>();

	/**
	 * The deck contains cards that are not in possession of any other players.
	 * The cards have to be added in random order, because cards are popped of
	 * the head of this queue.
	 */
	private LinkedList<Card> deck = new LinkedList<Card>();

	/**
	 * The current open card, on which players will have to find a matching
	 * card.
	 */
	private Card topCard;

	public CommunicationPipeline getCommunication() {
		return communication;
	}

	public void setCommunication(CommunicationPipeline communication) {
		this.communication = communication;
	}

	/**
	 * Adds a player to the game.
	 * 
	 * @param player
	 *            The player to add.
	 * @return Whether the player was successfully added.
	 */
	public boolean addPlayer(Player player) {
		return players.add(player);
	}

	/**
	 * Removes a player from the game.
	 * 
	 * @param player
	 *            The player to remove.
	 * @return Whether the player was successfully removed.
	 */
	public boolean removePlayer(Player player) {
		return players.remove(player);
	}

	/**
	 * Starts the game.
	 */
	public void start() {
		if (players.size() < MINIMAL_PLAYERS_NEEDED) {
			throw new UnoException("At least two players are needed to play Uno.");
		}

		this.createCards();
		Collections.shuffle(deck);
		this.deal();
	}

	/**
	 * Create all the cards in the deck.
	 */
	private void createCards() {
		Color[] colors = { Color.BLUE, Color.GREEN, Color.RED, Color.YELLOW };
		Rank[] ranks = { Rank.ONE, Rank.TWO, Rank.THREE, Rank.FOUR, Rank.FIVE, Rank.SIX, Rank.SEVEN, Rank.EIGHT,
				Rank.NINE, Rank.DRAW_TWO, Rank.REVERSE, Rank.SKIP };

		for (Color color : colors) {
			deck.add(new Card(color, Rank.ZERO));

			for (Rank rank : ranks) {
				deck.add(new Card(color, rank));
				deck.add(new Card(color, rank));
			}
		}

		for (int i = 0; i < NUM_WILDCARDS; i++) {
			deck.add(new Card(Color.WILDCARD, Rank.NONE));
			deck.add(new Card(Color.WILDCARD, Rank.DRAW_FOUR));
		}
	}

	/**
	 * Gives every player a bunch of cards and draws the first top card, to get
	 * the game going.
	 */
	private void deal() {
		// Done like this to mimic real life dealing situation.
		for (int i = 0; i < NUM_CARDS_PER_PLAYER; i++) {
			for (Player player : players) {
				player.addCard(deck.poll());
			}
		}

		this.topCard = deck.poll();
	}
}